/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2000-2002,2004,2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.impl.xpath;

import java.util.Vector;

import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;

/**
 * Bare minimum XPath parser.
 *
 * @author Andy Clark, IBM
 * @author Sunitha Reddy, Sun Microsystems
 * @xerces.internal
 */
public class XPath {

  //
  // Constants
  //

  private static final boolean DEBUG_ALL = false;

  private static final boolean DEBUG_XPATH_PARSE = DEBUG_ALL || false;

  private static final boolean DEBUG_ANY = DEBUG_XPATH_PARSE;

  //
  // Data
  //

  /**
   * Expression.
   */
  protected String fExpression;

  /**
   * Symbol table.
   */
  protected SymbolTable fSymbolTable;

  /**
   * Location paths.
   */
  protected LocationPath[] fLocationPaths;

  //
  // Constructors
  //

  /**
   * Constructs an XPath object from the specified expression.
   */
  public XPath(String xpath, SymbolTable symbolTable,
      NamespaceContext context)
      throws XPathException {
    fExpression = xpath;
    fSymbolTable = symbolTable;
    parseExpression(context);
  } // <init>(String,SymbolTable,NamespaceContext)

  //
  // Public methods
  //

  /**
   * Returns a representation of all location paths for this XPath.
   * XPath = locationPath ( '|' locationPath)
   */
  public LocationPath[] getLocationPaths() {
    LocationPath[] ret = new LocationPath[fLocationPaths.length];
    for (int i = 0; i < fLocationPaths.length; i++) {
      ret[i] = (LocationPath) fLocationPaths[i].clone();
    }
    return ret;
  } // getLocationPath(LocationPath)

  /**
   * Returns a representation of the first location path for this XPath.
   */
  public LocationPath getLocationPath() {
    return (LocationPath) fLocationPaths[0].clone();
  } // getLocationPath(LocationPath)

  //
  // Object methods
  //

  /**
   * Returns a string representation of this object.
   */
  public String toString() {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < fLocationPaths.length; i++) {
      if (i > 0) {
        buf.append("|");
      }
      buf.append(fLocationPaths[i].toString());
    }
    return buf.toString();
  } // toString():String

  //
  // Private methods
  //

  /**
   * Used by the {@link #parseExpression(NamespaceContext)} method
   * to verify the assumption.
   *
   * If <tt>b</tt> is false, this method throws XPathException
   * to report the error.
   */
  private static void check(boolean b) throws XPathException {
    if (!b) {
      throw new XPathException("c-general-xpath");
    }
  }

  /**
   * Used by the {@link #parseExpression(NamespaceContext)} method
   * to build a {@link LocationPath} object from the accumulated
   * {@link Step}s.
   */
  private LocationPath buildLocationPath(Vector stepsVector) throws XPathException {
    int size = stepsVector.size();
    check(size != 0);
    Step[] steps = new Step[size];
    stepsVector.copyInto(steps);
    stepsVector.removeAllElements();

    return new LocationPath(steps);
  }

  /**
   * This method is implemented by using the XPathExprScanner and
   * examining the list of tokens that it returns.
   */
  private void parseExpression(final NamespaceContext context)
      throws XPathException {

    // tokens
    final XPath.Tokens xtokens = new XPath.Tokens(fSymbolTable);

    // scanner
    XPath.Scanner scanner = new XPath.Scanner(fSymbolTable) {
      protected void addToken(XPath.Tokens tokens, int token)
          throws XPathException {
        if (
            token == XPath.Tokens.EXPRTOKEN_ATSIGN ||
                token == XPath.Tokens.EXPRTOKEN_AXISNAME_ATTRIBUTE ||
                token == XPath.Tokens.EXPRTOKEN_NAMETEST_QNAME ||
                token == XPath.Tokens.EXPRTOKEN_OPERATOR_SLASH ||
                token == XPath.Tokens.EXPRTOKEN_PERIOD ||
                token == XPath.Tokens.EXPRTOKEN_NAMETEST_ANY ||
                token == XPath.Tokens.EXPRTOKEN_NAMETEST_NAMESPACE ||
                token == XPath.Tokens.EXPRTOKEN_OPERATOR_DOUBLE_SLASH ||
                token == XPath.Tokens.EXPRTOKEN_OPERATOR_UNION ||
                token == XPath.Tokens.EXPRTOKEN_AXISNAME_CHILD ||
                token == XPath.Tokens.EXPRTOKEN_DOUBLE_COLON
            ) {
          super.addToken(tokens, token);
          return;
        }
        throw new XPathException("c-general-xpath");
      }
    };

    int length = fExpression.length();

    boolean success = scanner.scanExpr(fSymbolTable,
        xtokens, fExpression, 0, length);
    if (!success) {
      throw new XPathException("c-general-xpath");
    }

    //fTokens.dumpTokens();
    Vector stepsVector = new Vector();
    Vector locationPathsVector = new Vector();

    // true when the next token should be 'Step' (as defined in
    // the production rule [3] of XML Schema P1 section 3.11.6
    // if false, we are expecting either '|' or '/'.
    //
    // this is to make sure we can detect a token list like
    // 'abc' '/' '/' 'def' 'ghi'
    boolean expectingStep = true;
    boolean expectingDoubleColon = false;

    while (xtokens.hasMore()) {
      final int token = xtokens.nextToken();

      switch (token) {
        case XPath.Tokens.EXPRTOKEN_OPERATOR_UNION: {
          check(!expectingStep);
          locationPathsVector.addElement(buildLocationPath(stepsVector));
          expectingStep = true;
          break;
        }

        case XPath.Tokens.EXPRTOKEN_ATSIGN: {
          check(expectingStep);
          Step step = new Step(
              new Axis(Axis.ATTRIBUTE),
              parseNodeTest(xtokens.nextToken(), xtokens, context));
          stepsVector.addElement(step);
          expectingStep = false;
          break;
        }
        case XPath.Tokens.EXPRTOKEN_NAMETEST_ANY:
        case XPath.Tokens.EXPRTOKEN_NAMETEST_NAMESPACE:
        case XPath.Tokens.EXPRTOKEN_NAMETEST_QNAME: {
          check(expectingStep);
          Step step = new Step(
              new Axis(Axis.CHILD),
              parseNodeTest(token, xtokens, context));
          stepsVector.addElement(step);
          expectingStep = false;
          break;
        }

        case XPath.Tokens.EXPRTOKEN_PERIOD: {
          check(expectingStep);
          expectingStep = false;

          // unless this is the first step in this location path,
          // there's really no reason to keep them in LocationPath.
          // This amounts to shorten "a/././b/./c" to "a/b/c".
          // Also, the matcher fails to work correctly if XPath
          // has those redundant dots.
          if (stepsVector.size() == 0) {
            // build step
            Axis axis = new Axis(Axis.SELF);
            NodeTest nodeTest = new NodeTest(NodeTest.NODE);
            Step step = new Step(axis, nodeTest);
            stepsVector.addElement(step);

            if (xtokens.hasMore()
                && xtokens.peekToken() == XPath.Tokens.EXPRTOKEN_OPERATOR_DOUBLE_SLASH) {
              // consume '//'
              xtokens.nextToken();

              // build step
              axis = new Axis(Axis.DESCENDANT);
              nodeTest = new NodeTest(NodeTest.NODE);
              step = new Step(axis, nodeTest);
              stepsVector.addElement(step);
              expectingStep = true;
            }
          }
          break;
        }

        case XPath.Tokens.EXPRTOKEN_OPERATOR_DOUBLE_SLASH: {
          // this cannot appear in arbitrary position.
          // it is only allowed right after '.' when
          // '.' is the first token of a location path.
          throw new XPathException("c-general-xpath");
        }
        case XPath.Tokens.EXPRTOKEN_OPERATOR_SLASH: {
          check(!expectingStep);
          expectingStep = true;
          break;
        }
        case XPath.Tokens.EXPRTOKEN_AXISNAME_ATTRIBUTE: {
          check(expectingStep);
          expectingDoubleColon = true;

          if (xtokens.nextToken() == XPath.Tokens.EXPRTOKEN_DOUBLE_COLON) {
            Step step = new Step(
                new Axis(Axis.ATTRIBUTE),
                parseNodeTest(xtokens.nextToken(), xtokens, context));
            stepsVector.addElement(step);
            expectingStep = false;
            expectingDoubleColon = false;
          }
          break;
        }
        case XPath.Tokens.EXPRTOKEN_AXISNAME_CHILD: {
          check(expectingStep);
          expectingDoubleColon = true;
          break;
        }
        case XPath.Tokens.EXPRTOKEN_DOUBLE_COLON: {
          check(expectingStep);
          check(expectingDoubleColon);
          expectingDoubleColon = false;
          break;
        }
        default:
          // we should have covered all the tokens that we can possibly see.
          throw new XPathException("c-general-xpath");
      }
    }

    check(!expectingStep);

    locationPathsVector.addElement(buildLocationPath(stepsVector));

    // save location path
    fLocationPaths = new LocationPath[locationPathsVector.size()];
    locationPathsVector.copyInto(fLocationPaths);

    if (DEBUG_XPATH_PARSE) {
      System.out.println(">>> " + fLocationPaths);
    }

  } // parseExpression(SymbolTable,NamespaceContext)

  /**
   * Used by {@link #parseExpression} to parse a node test
   * from the token list.
   */
  private NodeTest parseNodeTest(int typeToken, Tokens xtokens, NamespaceContext context)
      throws XPathException {
    switch (typeToken) {
      case XPath.Tokens.EXPRTOKEN_NAMETEST_ANY:
        return new NodeTest(NodeTest.WILDCARD);

      case XPath.Tokens.EXPRTOKEN_NAMETEST_NAMESPACE:
      case XPath.Tokens.EXPRTOKEN_NAMETEST_QNAME:
        // consume QName token
        String prefix = xtokens.nextTokenAsString();
        String uri = null;
        if (context != null && prefix != XMLSymbols.EMPTY_STRING) {
          uri = context.getURI(prefix);
        }
        if (prefix != XMLSymbols.EMPTY_STRING && context != null && uri == null) {
          throw new XPathException("c-general-xpath-ns");
        }

        if (typeToken == XPath.Tokens.EXPRTOKEN_NAMETEST_NAMESPACE) {
          return new NodeTest(prefix, uri);
        }

        String localpart = xtokens.nextTokenAsString();
        String rawname = prefix != XMLSymbols.EMPTY_STRING
            ? fSymbolTable.addSymbol(prefix + ':' + localpart)
            : localpart;

        return new NodeTest(new QName(prefix, localpart, rawname, uri));

      default:
        // assertion error
        throw new XPathException("c-general-xpath");

    }
  }

  //
  // Classes
  //

  // location path information

  /**
   * A location path representation for an XPath expression.
   *
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  public static class LocationPath
      implements Cloneable {

    //
    // Data
    //

    /**
     * List of steps.
     */
    public Step[] steps;

    //
    // Constructors
    //

    /**
     * Creates a location path from a series of steps.
     */
    public LocationPath(Step[] steps) {
      this.steps = steps;
    } // <init>(Step[])

    /**
     * Copy constructor.
     */
    protected LocationPath(LocationPath path) {
      steps = new Step[path.steps.length];
      for (int i = 0; i < steps.length; i++) {
        steps[i] = (Step) path.steps[i].clone();
      }
    } // <init>(LocationPath)

    //
    // Object methods
    //

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
      StringBuffer str = new StringBuffer();
      for (int i = 0; i < steps.length; i++) {
        if (i > 0 && (steps[i - 1].axis.type != Axis.DESCENDANT
            && steps[i].axis.type != Axis.DESCENDANT)) {
          str.append('/');
        }
        str.append(steps[i].toString());
      }
      // DEBUG: This code is just for debugging and should *not*
      //        be left in because it will mess up hashcodes of
      //        serialized versions of this object. -Ac
      if (false) {
        str.append('[');
        String s = super.toString();
        str.append(s.substring(s.indexOf('@')));
        str.append(']');
      }
      return str.toString();
    } // toString():String

    /**
     * Returns a clone of this object.
     */
    public Object clone() {
      return new LocationPath(this);
    } // clone():Object

  } // class locationPath

  /**
   * A location path step comprised of an axis and node test.
   *
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  public static class Step
      implements Cloneable {

    //
    // Data
    //

    /**
     * Axis.
     */
    public Axis axis;

    /**
     * Node test.
     */
    public NodeTest nodeTest;

    //
    // Constructors
    //

    /**
     * Constructs a step from an axis and node test.
     */
    public Step(Axis axis, NodeTest nodeTest) {
      this.axis = axis;
      this.nodeTest = nodeTest;
    } // <init>(Axis,NodeTest)

    /**
     * Copy constructor.
     */
    protected Step(Step step) {
      axis = (Axis) step.axis.clone();
      nodeTest = (NodeTest) step.nodeTest.clone();
    } // <init>(Step)

    //
    // Object methods
    //

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
      if (axis.type == Axis.SELF) {
        return ".";
      }
      if (axis.type == Axis.ATTRIBUTE) {
        return "@" + nodeTest.toString();
      }
      if (axis.type == Axis.CHILD) {
        return nodeTest.toString();
      }
      if (axis.type == Axis.DESCENDANT) {
        return "//";
      }
      return "??? (" + axis.type + ')';
    } // toString():String

    /**
     * Returns a clone of this object.
     */
    public Object clone() {
      return new Step(this);
    } // clone():Object

  } // class Step

  /**
   * Axis.
   *
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  public static class Axis
      implements Cloneable {

    //
    // Constants
    //

    /**
     * Type: child.
     */
    public static final short CHILD = 1;

    /**
     * Type: attribute.
     */
    public static final short ATTRIBUTE = 2;

    /**
     * Type: self.
     */
    public static final short SELF = 3;


    /**
     * Type: descendant.
     */
    public static final short DESCENDANT = 4;
    //
    // Data
    //

    /**
     * Axis type.
     */
    public short type;

    //
    // Constructors
    //

    /**
     * Constructs an axis with the specified type.
     */
    public Axis(short type) {
      this.type = type;
    } // <init>(short)

    /**
     * Copy constructor.
     */
    protected Axis(Axis axis) {
      type = axis.type;
    } // <init>(Axis)

    //
    // Object methods
    //

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
      switch (type) {
        case CHILD:
          return "child";
        case ATTRIBUTE:
          return "attribute";
        case SELF:
          return "self";
        case DESCENDANT:
          return "descendant";
      }
      return "???";
    } // toString():String

    /**
     * Returns a clone of this object.
     */
    public Object clone() {
      return new Axis(this);
    } // clone():Object

  } // class Axis

  /**
   * Node test.
   *
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  public static class NodeTest
      implements Cloneable {

    //
    // Constants
    //

    /**
     * Type: qualified name.
     */
    public static final short QNAME = 1;

    /**
     * Type: wildcard.
     */
    public static final short WILDCARD = 2;

    /**
     * Type: node.
     */
    public static final short NODE = 3;

    /**
     * Type: namespace
     */
    public static final short NAMESPACE = 4;

    //
    // Data
    //

    /**
     * Node test type.
     */
    public short type;

    /**
     * Node qualified name.
     */
    public final QName name = new QName();

    //
    // Constructors
    //

    /**
     * Constructs a node test of type WILDCARD or NODE.
     */
    public NodeTest(short type) {
      this.type = type;
    } // <init>(int)

    /**
     * Constructs a node test of type QName.
     */
    public NodeTest(QName name) {
      this.type = QNAME;
      this.name.setValues(name);
    } // <init>(QName)

    /**
     * Constructs a node test of type Namespace.
     */
    public NodeTest(String prefix, String uri) {
      this.type = NAMESPACE;
      this.name.setValues(prefix, null, null, uri);
    } // <init>(String,String)

    /**
     * Copy constructor.
     */
    public NodeTest(NodeTest nodeTest) {
      type = nodeTest.type;
      name.setValues(nodeTest.name);
    } // <init>(NodeTest)

    //
    // Object methods
    //

    /**
     * Returns a string representation of this object.
     */
    public String toString() {

      switch (type) {
        case QNAME: {
          if (name.prefix.length() != 0) {
            if (name.uri != null) {
              return name.prefix + ':' + name.localpart;
            }
            return "{" + name.uri + '}' + name.prefix + ':' + name.localpart;
          }
          return name.localpart;
        }
        case NAMESPACE: {
          if (name.prefix.length() != 0) {
            if (name.uri != null) {
              return name.prefix + ":*";
            }
            return "{" + name.uri + '}' + name.prefix + ":*";
          }
          return "???:*";
        }
        case WILDCARD: {
          return "*";
        }
        case NODE: {
          return "node()";
        }
      }
      return "???";

    } // toString():String

    /**
     * Returns a clone of this object.
     */
    public Object clone() {
      return new NodeTest(this);
    } // clone():Object

  } // class NodeTest

  // xpath implementation

  // NOTE: The XPath implementation classes are kept internal because
  //       this implementation is just a temporary hack until a better
  //       and/or more appropriate implementation can be written.
  //       keeping the code in separate source files would "muddy" the
  //       CVS directory when it's not needed. -Ac

  /**
   * List of tokens.
   *
   * @author Glenn Marcy, IBM
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  private static final class Tokens {

    static final boolean DUMP_TOKENS = false;

    /**
     * [28] ExprToken ::= '(' | ')' | '[' | ']' | '.' | '..' | '@' | ',' | '::'
     * | NameTest | NodeType | Operator | FunctionName
     * | AxisName | Literal | Number | VariableReference
     */
    public static final int
        EXPRTOKEN_OPEN_PAREN = 0,
        EXPRTOKEN_CLOSE_PAREN = 1,
        EXPRTOKEN_OPEN_BRACKET = 2,
        EXPRTOKEN_CLOSE_BRACKET = 3,
        EXPRTOKEN_PERIOD = 4,
        EXPRTOKEN_DOUBLE_PERIOD = 5,
        EXPRTOKEN_ATSIGN = 6,
        EXPRTOKEN_COMMA = 7,
        EXPRTOKEN_DOUBLE_COLON = 8,
    //
    // [37] NameTest ::= '*' | NCName ':' '*' | QName
    //
    // followed by symbol handle of NCName or QName
    //
    EXPRTOKEN_NAMETEST_ANY = 9,
        EXPRTOKEN_NAMETEST_NAMESPACE = 10,
        EXPRTOKEN_NAMETEST_QNAME = 11,
    //
    // [38] NodeType ::= 'comment' | 'text' | 'processing-instruction' | 'node'
    //
    EXPRTOKEN_NODETYPE_COMMENT = 12,
        EXPRTOKEN_NODETYPE_TEXT = 13,
        EXPRTOKEN_NODETYPE_PI = 14,
        EXPRTOKEN_NODETYPE_NODE = 15,
    //
    // [32] Operator ::= OperatorName
    //                 | MultiplyOperator
    //                 | '/' | '//' | '|' | '+' | '-' | '=' | '!=' | '<' | '<=' | '>' | '>='
    // [33] OperatorName ::= 'and' | 'or' | 'mod' | 'div'
    // [34] MultiplyOperator ::= '*'
    //
    EXPRTOKEN_OPERATOR_AND = 16,
        EXPRTOKEN_OPERATOR_OR = 17,
        EXPRTOKEN_OPERATOR_MOD = 18,
        EXPRTOKEN_OPERATOR_DIV = 19,
        EXPRTOKEN_OPERATOR_MULT = 20,
        EXPRTOKEN_OPERATOR_SLASH = 21,
        EXPRTOKEN_OPERATOR_DOUBLE_SLASH = 22,
        EXPRTOKEN_OPERATOR_UNION = 23,
        EXPRTOKEN_OPERATOR_PLUS = 24,
        EXPRTOKEN_OPERATOR_MINUS = 25,
        EXPRTOKEN_OPERATOR_EQUAL = 26,
        EXPRTOKEN_OPERATOR_NOT_EQUAL = 27,
        EXPRTOKEN_OPERATOR_LESS = 28,
        EXPRTOKEN_OPERATOR_LESS_EQUAL = 29,
        EXPRTOKEN_OPERATOR_GREATER = 30,
        EXPRTOKEN_OPERATOR_GREATER_EQUAL = 31,

    //EXPRTOKEN_FIRST_OPERATOR                = EXPRTOKEN_OPERATOR_AND,
    //EXPRTOKEN_LAST_OPERATOR                 = EXPRTOKEN_OPERATOR_GREATER_EQUAL,

    //
    // [35] FunctionName ::= QName - NodeType
    //
    // followed by symbol handle
    //
    EXPRTOKEN_FUNCTION_NAME = 32,
    //
    // [6] AxisName ::= 'ancestor' | 'ancestor-or-self'
    //                | 'attribute'
    //                | 'child'
    //                | 'descendant' | 'descendant-or-self'
    //                | 'following' | 'following-sibling'
    //                | 'namespace'
    //                | 'parent'
    //                | 'preceding' | 'preceding-sibling'
    //                | 'self'
    //
    EXPRTOKEN_AXISNAME_ANCESTOR = 33,
        EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF = 34,
        EXPRTOKEN_AXISNAME_ATTRIBUTE = 35,
        EXPRTOKEN_AXISNAME_CHILD = 36,
        EXPRTOKEN_AXISNAME_DESCENDANT = 37,
        EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF = 38,
        EXPRTOKEN_AXISNAME_FOLLOWING = 39,
        EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING = 40,
        EXPRTOKEN_AXISNAME_NAMESPACE = 41,
        EXPRTOKEN_AXISNAME_PARENT = 42,
        EXPRTOKEN_AXISNAME_PRECEDING = 43,
        EXPRTOKEN_AXISNAME_PRECEDING_SIBLING = 44,
        EXPRTOKEN_AXISNAME_SELF = 45,
    //
    // [29] Literal ::= '"' [^"]* '"' | "'" [^']* "'"
    //
    // followed by symbol handle for literal
    //
    EXPRTOKEN_LITERAL = 46,
    //
    // [30] Number ::= Digits ('.' Digits?)? | '.' Digits
    // [31] Digits ::= [0-9]+
    //
    // followed by number handle
    //
    EXPRTOKEN_NUMBER = 47,
    //
    // [36] VariableReference ::= '$' QName
    //
    // followed by symbol handle for QName
    //
    EXPRTOKEN_VARIABLE_REFERENCE = 48;

    private static final String[] fgTokenNames = {
        "EXPRTOKEN_OPEN_PAREN",
        "EXPRTOKEN_CLOSE_PAREN",
        "EXPRTOKEN_OPEN_BRACKET",
        "EXPRTOKEN_CLOSE_BRACKET",
        "EXPRTOKEN_PERIOD",
        "EXPRTOKEN_DOUBLE_PERIOD",
        "EXPRTOKEN_ATSIGN",
        "EXPRTOKEN_COMMA",
        "EXPRTOKEN_DOUBLE_COLON",
        "EXPRTOKEN_NAMETEST_ANY",
        "EXPRTOKEN_NAMETEST_NAMESPACE",
        "EXPRTOKEN_NAMETEST_QNAME",
        "EXPRTOKEN_NODETYPE_COMMENT",
        "EXPRTOKEN_NODETYPE_TEXT",
        "EXPRTOKEN_NODETYPE_PI",
        "EXPRTOKEN_NODETYPE_NODE",
        "EXPRTOKEN_OPERATOR_AND",
        "EXPRTOKEN_OPERATOR_OR",
        "EXPRTOKEN_OPERATOR_MOD",
        "EXPRTOKEN_OPERATOR_DIV",
        "EXPRTOKEN_OPERATOR_MULT",
        "EXPRTOKEN_OPERATOR_SLASH",
        "EXPRTOKEN_OPERATOR_DOUBLE_SLASH",
        "EXPRTOKEN_OPERATOR_UNION",
        "EXPRTOKEN_OPERATOR_PLUS",
        "EXPRTOKEN_OPERATOR_MINUS",
        "EXPRTOKEN_OPERATOR_EQUAL",
        "EXPRTOKEN_OPERATOR_NOT_EQUAL",
        "EXPRTOKEN_OPERATOR_LESS",
        "EXPRTOKEN_OPERATOR_LESS_EQUAL",
        "EXPRTOKEN_OPERATOR_GREATER",
        "EXPRTOKEN_OPERATOR_GREATER_EQUAL",
        "EXPRTOKEN_FUNCTION_NAME",
        "EXPRTOKEN_AXISNAME_ANCESTOR",
        "EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF",
        "EXPRTOKEN_AXISNAME_ATTRIBUTE",
        "EXPRTOKEN_AXISNAME_CHILD",
        "EXPRTOKEN_AXISNAME_DESCENDANT",
        "EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF",
        "EXPRTOKEN_AXISNAME_FOLLOWING",
        "EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING",
        "EXPRTOKEN_AXISNAME_NAMESPACE",
        "EXPRTOKEN_AXISNAME_PARENT",
        "EXPRTOKEN_AXISNAME_PRECEDING",
        "EXPRTOKEN_AXISNAME_PRECEDING_SIBLING",
        "EXPRTOKEN_AXISNAME_SELF",
        "EXPRTOKEN_LITERAL",
        "EXPRTOKEN_NUMBER",
        "EXPRTOKEN_VARIABLE_REFERENCE"
    };

    /**
     *
     */
    private static final int INITIAL_TOKEN_COUNT = 1 << 8;
    private int[] fTokens = new int[INITIAL_TOKEN_COUNT];
    private int fTokenCount = 0;    // for writing

    private SymbolTable fSymbolTable;

    // REVISIT: Code something better here. -Ac
    private java.util.Hashtable fSymbolMapping = new java.util.Hashtable();

    // REVISIT: Code something better here. -Ac
    private java.util.Hashtable fTokenNames = new java.util.Hashtable();

    /**
     * Current position in the token list.
     */
    private int fCurrentTokenIndex;

    //
    // Constructors
    //

    public Tokens(SymbolTable symbolTable) {
      fSymbolTable = symbolTable;
      final String[] symbols = {
          "ancestor", "ancestor-or-self", "attribute",
          "child", "descendant", "descendant-or-self",
          "following", "following-sibling", "namespace",
          "parent", "preceding", "preceding-sibling",
          "self",
      };
      for (int i = 0; i < symbols.length; i++) {
        fSymbolMapping.put(fSymbolTable.addSymbol(symbols[i]), new Integer(i));
      }
      fTokenNames.put(new Integer(EXPRTOKEN_OPEN_PAREN), "EXPRTOKEN_OPEN_PAREN");
      fTokenNames.put(new Integer(EXPRTOKEN_CLOSE_PAREN), "EXPRTOKEN_CLOSE_PAREN");
      fTokenNames.put(new Integer(EXPRTOKEN_OPEN_BRACKET), "EXPRTOKEN_OPEN_BRACKET");
      fTokenNames.put(new Integer(EXPRTOKEN_CLOSE_BRACKET), "EXPRTOKEN_CLOSE_BRACKET");
      fTokenNames.put(new Integer(EXPRTOKEN_PERIOD), "EXPRTOKEN_PERIOD");
      fTokenNames.put(new Integer(EXPRTOKEN_DOUBLE_PERIOD), "EXPRTOKEN_DOUBLE_PERIOD");
      fTokenNames.put(new Integer(EXPRTOKEN_ATSIGN), "EXPRTOKEN_ATSIGN");
      fTokenNames.put(new Integer(EXPRTOKEN_COMMA), "EXPRTOKEN_COMMA");
      fTokenNames.put(new Integer(EXPRTOKEN_DOUBLE_COLON), "EXPRTOKEN_DOUBLE_COLON");
      fTokenNames.put(new Integer(EXPRTOKEN_NAMETEST_ANY), "EXPRTOKEN_NAMETEST_ANY");
      fTokenNames.put(new Integer(EXPRTOKEN_NAMETEST_NAMESPACE), "EXPRTOKEN_NAMETEST_NAMESPACE");
      fTokenNames.put(new Integer(EXPRTOKEN_NAMETEST_QNAME), "EXPRTOKEN_NAMETEST_QNAME");
      fTokenNames.put(new Integer(EXPRTOKEN_NODETYPE_COMMENT), "EXPRTOKEN_NODETYPE_COMMENT");
      fTokenNames.put(new Integer(EXPRTOKEN_NODETYPE_TEXT), "EXPRTOKEN_NODETYPE_TEXT");
      fTokenNames.put(new Integer(EXPRTOKEN_NODETYPE_PI), "EXPRTOKEN_NODETYPE_PI");
      fTokenNames.put(new Integer(EXPRTOKEN_NODETYPE_NODE), "EXPRTOKEN_NODETYPE_NODE");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_AND), "EXPRTOKEN_OPERATOR_AND");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_OR), "EXPRTOKEN_OPERATOR_OR");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_MOD), "EXPRTOKEN_OPERATOR_MOD");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_DIV), "EXPRTOKEN_OPERATOR_DIV");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_MULT), "EXPRTOKEN_OPERATOR_MULT");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_SLASH), "EXPRTOKEN_OPERATOR_SLASH");
      fTokenNames
          .put(new Integer(EXPRTOKEN_OPERATOR_DOUBLE_SLASH), "EXPRTOKEN_OPERATOR_DOUBLE_SLASH");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_UNION), "EXPRTOKEN_OPERATOR_UNION");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_PLUS), "EXPRTOKEN_OPERATOR_PLUS");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_MINUS), "EXPRTOKEN_OPERATOR_MINUS");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_EQUAL), "EXPRTOKEN_OPERATOR_EQUAL");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_NOT_EQUAL), "EXPRTOKEN_OPERATOR_NOT_EQUAL");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_LESS), "EXPRTOKEN_OPERATOR_LESS");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_LESS_EQUAL), "EXPRTOKEN_OPERATOR_LESS_EQUAL");
      fTokenNames.put(new Integer(EXPRTOKEN_OPERATOR_GREATER), "EXPRTOKEN_OPERATOR_GREATER");
      fTokenNames
          .put(new Integer(EXPRTOKEN_OPERATOR_GREATER_EQUAL), "EXPRTOKEN_OPERATOR_GREATER_EQUAL");
      fTokenNames.put(new Integer(EXPRTOKEN_FUNCTION_NAME), "EXPRTOKEN_FUNCTION_NAME");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_ANCESTOR), "EXPRTOKEN_AXISNAME_ANCESTOR");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF),
          "EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_ATTRIBUTE), "EXPRTOKEN_AXISNAME_ATTRIBUTE");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_CHILD), "EXPRTOKEN_AXISNAME_CHILD");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_DESCENDANT), "EXPRTOKEN_AXISNAME_DESCENDANT");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF),
          "EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_FOLLOWING), "EXPRTOKEN_AXISNAME_FOLLOWING");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING),
          "EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_NAMESPACE), "EXPRTOKEN_AXISNAME_NAMESPACE");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_PARENT), "EXPRTOKEN_AXISNAME_PARENT");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_PRECEDING), "EXPRTOKEN_AXISNAME_PRECEDING");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_PRECEDING_SIBLING),
          "EXPRTOKEN_AXISNAME_PRECEDING_SIBLING");
      fTokenNames.put(new Integer(EXPRTOKEN_AXISNAME_SELF), "EXPRTOKEN_AXISNAME_SELF");
      fTokenNames.put(new Integer(EXPRTOKEN_LITERAL), "EXPRTOKEN_LITERAL");
      fTokenNames.put(new Integer(EXPRTOKEN_NUMBER), "EXPRTOKEN_NUMBER");
      fTokenNames.put(new Integer(EXPRTOKEN_VARIABLE_REFERENCE), "EXPRTOKEN_VARIABLE_REFERENCE");
    }

    //
    // Public methods
    //

    //        public String getTokenName(int token) {
//            if (token < 0 || token >= fgTokenNames.length)
//                return null;
//            return fgTokenNames[token];
//        }
//
    public String getTokenString(int token) {
      return (String) fTokenNames.get(new Integer(token));
    }

    public void addToken(String tokenStr) {
      Integer tokenInt = (Integer) fTokenNames.get(tokenStr);
      if (tokenInt == null) {
        tokenInt = new Integer(fTokenNames.size());
        fTokenNames.put(tokenInt, tokenStr);
      }
      addToken(tokenInt.intValue());
    }

    public void addToken(int token) {
      try {
        fTokens[fTokenCount] = token;
      } catch (ArrayIndexOutOfBoundsException ex) {
        int[] oldList = fTokens;
        fTokens = new int[fTokenCount << 1];
        System.arraycopy(oldList, 0, fTokens, 0, fTokenCount);
        fTokens[fTokenCount] = token;
      }
      fTokenCount++;
    }
//        public int getTokenCount() {
//            return fTokenCount;
//        }
//        public int getToken(int tokenIndex) {
//            return fTokens[tokenIndex];
//        }

    /**
     * Resets the current position to the head of the token list.
     */
    public void rewind() {
      fCurrentTokenIndex = 0;
    }

    /**
     * Returns true if the {@link #getNextToken()} method
     * returns a valid token.
     */
    public boolean hasMore() {
      return fCurrentTokenIndex < fTokenCount;
    }

    /**
     * Obtains the token at the current position, then advance
     * the current position by one.
     *
     * If there's no such next token, this method throws
     * <tt>new XPathException("c-general-xpath");</tt>.
     */
    public int nextToken() throws XPathException {
      if (fCurrentTokenIndex == fTokenCount) {
        throw new XPathException("c-general-xpath");
      }
      return fTokens[fCurrentTokenIndex++];
    }

    /**
     * Obtains the token at the current position, without advancing
     * the current position.
     *
     * If there's no such next token, this method throws
     * <tt>new XPathException("c-general-xpath");</tt>.
     */
    public int peekToken() throws XPathException {
      if (fCurrentTokenIndex == fTokenCount) {
        throw new XPathException("c-general-xpath");
      }
      return fTokens[fCurrentTokenIndex];
    }

    /**
     * Obtains the token at the current position as a String.
     *
     * If there's no current token or if the current token
     * is not a string token, this method throws
     * <tt>new XPathException("c-general-xpath");</tt>.
     */
    public String nextTokenAsString() throws XPathException {
      String s = getTokenString(nextToken());
      if (s == null) {
        throw new XPathException("c-general-xpath");
      }
      return s;
    }

    public void dumpTokens() {
      //if (DUMP_TOKENS) {
      for (int i = 0; i < fTokenCount; i++) {
        switch (fTokens[i]) {
          case EXPRTOKEN_OPEN_PAREN:
            System.out.print("<OPEN_PAREN/>");
            break;
          case EXPRTOKEN_CLOSE_PAREN:
            System.out.print("<CLOSE_PAREN/>");
            break;
          case EXPRTOKEN_OPEN_BRACKET:
            System.out.print("<OPEN_BRACKET/>");
            break;
          case EXPRTOKEN_CLOSE_BRACKET:
            System.out.print("<CLOSE_BRACKET/>");
            break;
          case EXPRTOKEN_PERIOD:
            System.out.print("<PERIOD/>");
            break;
          case EXPRTOKEN_DOUBLE_PERIOD:
            System.out.print("<DOUBLE_PERIOD/>");
            break;
          case EXPRTOKEN_ATSIGN:
            System.out.print("<ATSIGN/>");
            break;
          case EXPRTOKEN_COMMA:
            System.out.print("<COMMA/>");
            break;
          case EXPRTOKEN_DOUBLE_COLON:
            System.out.print("<DOUBLE_COLON/>");
            break;
          case EXPRTOKEN_NAMETEST_ANY:
            System.out.print("<NAMETEST_ANY/>");
            break;
          case EXPRTOKEN_NAMETEST_NAMESPACE:
            System.out.print("<NAMETEST_NAMESPACE");
            System.out.print(" prefix=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          case EXPRTOKEN_NAMETEST_QNAME:
            System.out.print("<NAMETEST_QNAME");
            if (fTokens[++i] != -1) {
              System.out.print(" prefix=\"" + getTokenString(fTokens[i]) + "\"");
            }
            System.out.print(" localpart=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          case EXPRTOKEN_NODETYPE_COMMENT:
            System.out.print("<NODETYPE_COMMENT/>");
            break;
          case EXPRTOKEN_NODETYPE_TEXT:
            System.out.print("<NODETYPE_TEXT/>");
            break;
          case EXPRTOKEN_NODETYPE_PI:
            System.out.print("<NODETYPE_PI/>");
            break;
          case EXPRTOKEN_NODETYPE_NODE:
            System.out.print("<NODETYPE_NODE/>");
            break;
          case EXPRTOKEN_OPERATOR_AND:
            System.out.print("<OPERATOR_AND/>");
            break;
          case EXPRTOKEN_OPERATOR_OR:
            System.out.print("<OPERATOR_OR/>");
            break;
          case EXPRTOKEN_OPERATOR_MOD:
            System.out.print("<OPERATOR_MOD/>");
            break;
          case EXPRTOKEN_OPERATOR_DIV:
            System.out.print("<OPERATOR_DIV/>");
            break;
          case EXPRTOKEN_OPERATOR_MULT:
            System.out.print("<OPERATOR_MULT/>");
            break;
          case EXPRTOKEN_OPERATOR_SLASH:
            System.out.print("<OPERATOR_SLASH/>");
            if (i + 1 < fTokenCount) {
              System.out.println();
              System.out.print("  ");
            }
            break;
          case EXPRTOKEN_OPERATOR_DOUBLE_SLASH:
            System.out.print("<OPERATOR_DOUBLE_SLASH/>");
            break;
          case EXPRTOKEN_OPERATOR_UNION:
            System.out.print("<OPERATOR_UNION/>");
            break;
          case EXPRTOKEN_OPERATOR_PLUS:
            System.out.print("<OPERATOR_PLUS/>");
            break;
          case EXPRTOKEN_OPERATOR_MINUS:
            System.out.print("<OPERATOR_MINUS/>");
            break;
          case EXPRTOKEN_OPERATOR_EQUAL:
            System.out.print("<OPERATOR_EQUAL/>");
            break;
          case EXPRTOKEN_OPERATOR_NOT_EQUAL:
            System.out.print("<OPERATOR_NOT_EQUAL/>");
            break;
          case EXPRTOKEN_OPERATOR_LESS:
            System.out.print("<OPERATOR_LESS/>");
            break;
          case EXPRTOKEN_OPERATOR_LESS_EQUAL:
            System.out.print("<OPERATOR_LESS_EQUAL/>");
            break;
          case EXPRTOKEN_OPERATOR_GREATER:
            System.out.print("<OPERATOR_GREATER/>");
            break;
          case EXPRTOKEN_OPERATOR_GREATER_EQUAL:
            System.out.print("<OPERATOR_GREATER_EQUAL/>");
            break;
          case EXPRTOKEN_FUNCTION_NAME:
            System.out.print("<FUNCTION_NAME");
            if (fTokens[++i] != -1) {
              System.out.print(" prefix=\"" + getTokenString(fTokens[i]) + "\"");
            }
            System.out.print(" localpart=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          case EXPRTOKEN_AXISNAME_ANCESTOR:
            System.out.print("<AXISNAME_ANCESTOR/>");
            break;
          case EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF:
            System.out.print("<AXISNAME_ANCESTOR_OR_SELF/>");
            break;
          case EXPRTOKEN_AXISNAME_ATTRIBUTE:
            System.out.print("<AXISNAME_ATTRIBUTE/>");
            break;
          case EXPRTOKEN_AXISNAME_CHILD:
            System.out.print("<AXISNAME_CHILD/>");
            break;
          case EXPRTOKEN_AXISNAME_DESCENDANT:
            System.out.print("<AXISNAME_DESCENDANT/>");
            break;
          case EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF:
            System.out.print("<AXISNAME_DESCENDANT_OR_SELF/>");
            break;
          case EXPRTOKEN_AXISNAME_FOLLOWING:
            System.out.print("<AXISNAME_FOLLOWING/>");
            break;
          case EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING:
            System.out.print("<AXISNAME_FOLLOWING_SIBLING/>");
            break;
          case EXPRTOKEN_AXISNAME_NAMESPACE:
            System.out.print("<AXISNAME_NAMESPACE/>");
            break;
          case EXPRTOKEN_AXISNAME_PARENT:
            System.out.print("<AXISNAME_PARENT/>");
            break;
          case EXPRTOKEN_AXISNAME_PRECEDING:
            System.out.print("<AXISNAME_PRECEDING/>");
            break;
          case EXPRTOKEN_AXISNAME_PRECEDING_SIBLING:
            System.out.print("<AXISNAME_PRECEDING_SIBLING/>");
            break;
          case EXPRTOKEN_AXISNAME_SELF:
            System.out.print("<AXISNAME_SELF/>");
            break;
          case EXPRTOKEN_LITERAL:
            System.out.print("<LITERAL");
            System.out.print(" value=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          case EXPRTOKEN_NUMBER:
            System.out.print("<NUMBER");
            System.out.print(" whole=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print(" part=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          case EXPRTOKEN_VARIABLE_REFERENCE:
            System.out.print("<VARIABLE_REFERENCE");
            if (fTokens[++i] != -1) {
              System.out.print(" prefix=\"" + getTokenString(fTokens[i]) + "\"");
            }
            System.out.print(" localpart=\"" + getTokenString(fTokens[++i]) + "\"");
            System.out.print("/>");
            break;
          default:
            System.out.println("<???/>");
        }
      }
      System.out.println();
      //}
    }

  } // class Tokens

  /**
   * @author Glenn Marcy, IBM
   * @author Andy Clark, IBM
   * @xerces.internal
   */
  private static class Scanner {

    /**
     * 7-bit ASCII subset
     *
     * 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
     * 0,  0,  0,  0,  0,  0,  0,  0,  0, HT, LF,  0,  0, CR,  0,  0,  // 0
     * 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
     * SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  // 2
     * 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  // 3
     *
     * @, A,  B,  C,  D,  E,  F,  G,  H,  I,  J,  K,  L,  M,  N,  O,  // 4 P,  Q,  R,  S,  T,  U,
     * V,  W,  X,  Y,  Z,  [,  \,  ],  ^,  _,  // 5 `,  a,  b,  c,  d,  e,  f,  g,  h,  i,  j,  k,
     * l,  m,  n,  o,  // 6 p,  q,  r,  s,  t,  u,  v,  w,  x,  y,  z,  {,  |,  },  ~, DEL  // 7
     */
    private static final byte
        CHARTYPE_INVALID = 0,   // invalid XML character
        CHARTYPE_OTHER = 1,   // not special - one of "#%&;?\^`{}~" or DEL
        CHARTYPE_WHITESPACE = 2,   // one of "\t\n\r " (0x09, 0x0A, 0x0D, 0x20)
        CHARTYPE_EXCLAMATION = 3,   // '!' (0x21)
        CHARTYPE_QUOTE = 4,   // '\"' or '\'' (0x22 and 0x27)
        CHARTYPE_DOLLAR = 5,   // '$' (0x24)
        CHARTYPE_OPEN_PAREN = 6,   // '(' (0x28)
        CHARTYPE_CLOSE_PAREN = 7,   // ')' (0x29)
        CHARTYPE_STAR = 8,   // '*' (0x2A)
        CHARTYPE_PLUS = 9,   // '+' (0x2B)
        CHARTYPE_COMMA = 10,   // ',' (0x2C)
        CHARTYPE_MINUS = 11,   // '-' (0x2D)
        CHARTYPE_PERIOD = 12,   // '.' (0x2E)
        CHARTYPE_SLASH = 13,   // '/' (0x2F)
        CHARTYPE_DIGIT = 14,   // '0'-'9' (0x30 to 0x39)
        CHARTYPE_COLON = 15,   // ':' (0x3A)
        CHARTYPE_LESS = 16,   // '<' (0x3C)
        CHARTYPE_EQUAL = 17,   // '=' (0x3D)
        CHARTYPE_GREATER = 18,   // '>' (0x3E)
        CHARTYPE_ATSIGN = 19,   // '@' (0x40)
        CHARTYPE_LETTER = 20,   // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A)
        CHARTYPE_OPEN_BRACKET = 21,   // '[' (0x5B)
        CHARTYPE_CLOSE_BRACKET = 22,   // ']' (0x5D)
        CHARTYPE_UNDERSCORE = 23,   // '_' (0x5F)
        CHARTYPE_UNION = 24,   // '|' (0x7C)
        CHARTYPE_NONASCII = 25;   // Non-ASCII Unicode codepoint (>= 0x80)

    private static final byte[] fASCIICharMap = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        2, 3, 4, 1, 5, 1, 1, 4, 6, 7, 8, 9, 10, 11, 12, 13,
        14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 1, 16, 17, 18, 1,
        19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
        20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 1, 22, 1, 23,
        1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
        20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 24, 1, 1, 1
    };

    /**
     * Symbol literals
     */

    //
    // Data
    //

    /**
     * Symbol table.
     */
    private SymbolTable fSymbolTable;

    // symbols

    private static final String fAndSymbol = "and".intern();
    private static final String fOrSymbol = "or".intern();
    private static final String fModSymbol = "mod".intern();
    private static final String fDivSymbol = "div".intern();

    private static final String fCommentSymbol = "comment".intern();
    private static final String fTextSymbol = "text".intern();
    private static final String fPISymbol = "processing-instruction".intern();
    private static final String fNodeSymbol = "node".intern();

    private static final String fAncestorSymbol = "ancestor".intern();
    private static final String fAncestorOrSelfSymbol = "ancestor-or-self".intern();
    private static final String fAttributeSymbol = "attribute".intern();
    private static final String fChildSymbol = "child".intern();
    private static final String fDescendantSymbol = "descendant".intern();
    private static final String fDescendantOrSelfSymbol = "descendant-or-self".intern();
    private static final String fFollowingSymbol = "following".intern();
    private static final String fFollowingSiblingSymbol = "following-sibling".intern();
    private static final String fNamespaceSymbol = "namespace".intern();
    private static final String fParentSymbol = "parent".intern();
    private static final String fPrecedingSymbol = "preceding".intern();
    private static final String fPrecedingSiblingSymbol = "preceding-sibling".intern();
    private static final String fSelfSymbol = "self".intern();

    //
    // Constructors
    //

    /**
     * Constructs an XPath expression scanner.
     */
    public Scanner(SymbolTable symbolTable) {

      // save pool and tokens
      fSymbolTable = symbolTable;

    } // <init>(SymbolTable)

    /**
     *
     */
    public boolean scanExpr(SymbolTable symbolTable,
        XPath.Tokens tokens, String data,
        int currentOffset, int endOffset)
        throws XPathException {

      int nameOffset;
      String nameHandle, prefixHandle;
      boolean starIsMultiplyOperator = false;
      int ch;

      while (true) {
        if (currentOffset == endOffset) {
          break;
        }
        ch = data.charAt(currentOffset);
        //
        // [39] ExprWhitespace ::= S
        //
        while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
          if (++currentOffset == endOffset) {
            break;
          }
          ch = data.charAt(currentOffset);
        }
        if (currentOffset == endOffset) {
          break;
        }
        //
        // [28] ExprToken ::= '(' | ')' | '[' | ']' | '.' | '..' | '@' | ',' | '::'
        //                  | NameTest | NodeType | Operator | FunctionName
        //                  | AxisName | Literal | Number | VariableReference
        //
        byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII : fASCIICharMap[ch];
        switch (chartype) {
          case CHARTYPE_OPEN_PAREN:       // '('
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPEN_PAREN);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_CLOSE_PAREN:      // ')'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_CLOSE_PAREN);
            starIsMultiplyOperator = true;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_OPEN_BRACKET:     // '['
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPEN_BRACKET);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_CLOSE_BRACKET:    // ']'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_CLOSE_BRACKET);
            starIsMultiplyOperator = true;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          //
          // [30] Number ::= Digits ('.' Digits?)? | '.' Digits
          //                                         ^^^^^^^^^^
          //
          case CHARTYPE_PERIOD:           // '.', '..' or '.' Digits
            if (currentOffset + 1 == endOffset) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_PERIOD);
              starIsMultiplyOperator = true;
              currentOffset++;
              break;
            }
            ch = data.charAt(currentOffset + 1);
            if (ch == '.') {            // '..'
              addToken(tokens, XPath.Tokens.EXPRTOKEN_DOUBLE_PERIOD);
              starIsMultiplyOperator = true;
              currentOffset += 2;
            } else if (ch >= '0' && ch <= '9') {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_NUMBER);
              starIsMultiplyOperator = true;
              currentOffset = scanNumber(tokens, data, endOffset, currentOffset/*, encoding*/);
            } else if (ch == '/') {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_PERIOD);
              starIsMultiplyOperator = true;
              currentOffset++;
            } else if (ch == '|') {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_PERIOD);
              starIsMultiplyOperator = true;
              currentOffset++;
              break;
            } else if (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
              // this is legal if the next token is non-existent or |
              do {
                if (++currentOffset == endOffset) {
                  break;
                }
                ch = data.charAt(currentOffset);
              } while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D);
              if (currentOffset == endOffset || ch == '|' || ch == '/') {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_PERIOD);
                starIsMultiplyOperator = true;
                break;
              }
              throw new XPathException("c-general-xpath");
            } else {                    // '.'
              throw new XPathException("c-general-xpath");
            }
            if (currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_ATSIGN:           // '@'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_ATSIGN);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_COMMA:            // ','
            addToken(tokens, XPath.Tokens.EXPRTOKEN_COMMA);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_COLON:            // '::'
            if (++currentOffset == endOffset) {
              // System.out.println("abort 1a");
              return false; // REVISIT
            }
            ch = data.charAt(currentOffset);
            if (ch != ':') {
              // System.out.println("abort 1b");
              return false; // REVISIT
            }
            addToken(tokens, XPath.Tokens.EXPRTOKEN_DOUBLE_COLON);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_SLASH:            // '/' and '//'
            if (++currentOffset == endOffset) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_SLASH);
              starIsMultiplyOperator = false;
              break;
            }
            ch = data.charAt(currentOffset);
            if (ch == '/') { // '//'
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_DOUBLE_SLASH);
              starIsMultiplyOperator = false;
              if (++currentOffset == endOffset) {
                break;
              }
            } else {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_SLASH);
              starIsMultiplyOperator = false;
            }
            break;
          case CHARTYPE_UNION:            // '|'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_UNION);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_PLUS:             // '+'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_PLUS);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_MINUS:            // '-'
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_MINUS);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_EQUAL:            // '='
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_EQUAL);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_EXCLAMATION:      // '!='
            if (++currentOffset == endOffset) {
              // System.out.println("abort 2a");
              return false; // REVISIT
            }
            ch = data.charAt(currentOffset);
            if (ch != '=') {
              // System.out.println("abort 2b");
              return false; // REVISIT
            }
            addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_NOT_EQUAL);
            starIsMultiplyOperator = false;
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          case CHARTYPE_LESS: // '<' and '<='
            if (++currentOffset == endOffset) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_LESS);
              starIsMultiplyOperator = false;
              break;
            }
            ch = data.charAt(currentOffset);
            if (ch == '=') { // '<='
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_LESS_EQUAL);
              starIsMultiplyOperator = false;
              if (++currentOffset == endOffset) {
                break;
              }
            } else {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_LESS);
              starIsMultiplyOperator = false;
            }
            break;
          case CHARTYPE_GREATER: // '>' and '>='
            if (++currentOffset == endOffset) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_GREATER);
              starIsMultiplyOperator = false;
              break;
            }
            ch = data.charAt(currentOffset);
            if (ch == '=') { // '>='
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_GREATER_EQUAL);
              starIsMultiplyOperator = false;
              if (++currentOffset == endOffset) {
                break;
              }
            } else {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_GREATER);
              starIsMultiplyOperator = false;
            }
            break;
          //
          // [29] Literal ::= '"' [^"]* '"' | "'" [^']* "'"
          //
          case CHARTYPE_QUOTE:            // '\"' or '\''
            int qchar = ch;
            if (++currentOffset == endOffset) {
              // System.out.println("abort 2c");
              return false; // REVISIT
            }
            ch = data.charAt(currentOffset);
            int litOffset = currentOffset;
            while (ch != qchar) {
              if (++currentOffset == endOffset) {
                // System.out.println("abort 2d");
                return false; // REVISIT
              }
              ch = data.charAt(currentOffset);
            }
            int litLength = currentOffset - litOffset;
            addToken(tokens, XPath.Tokens.EXPRTOKEN_LITERAL);
            starIsMultiplyOperator = true;
            tokens
                .addToken(symbolTable.addSymbol(data.substring(litOffset, litOffset + litLength)));
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          //
          // [30] Number ::= Digits ('.' Digits?)? | '.' Digits
          // [31] Digits ::= [0-9]+
          //
          case CHARTYPE_DIGIT:
            addToken(tokens, XPath.Tokens.EXPRTOKEN_NUMBER);
            starIsMultiplyOperator = true;
            currentOffset = scanNumber(tokens, data, endOffset, currentOffset/*, encoding*/);
            break;
          //
          // [36] VariableReference ::= '$' QName
          //
          case CHARTYPE_DOLLAR:
            if (++currentOffset == endOffset) {
              // System.out.println("abort 3a");
              return false; // REVISIT
            }
            nameOffset = currentOffset;
            currentOffset = scanNCName(data, endOffset, currentOffset);
            if (currentOffset == nameOffset) {
              // System.out.println("abort 3b");
              return false; // REVISIT
            }
            if (currentOffset < endOffset) {
              ch = data.charAt(currentOffset);
            } else {
              ch = -1;
            }
            nameHandle = symbolTable.addSymbol(data.substring(nameOffset, currentOffset));
            if (ch != ':') {
              prefixHandle = XMLSymbols.EMPTY_STRING;
            } else {
              prefixHandle = nameHandle;
              if (++currentOffset == endOffset) {
                // System.out.println("abort 4a");
                return false; // REVISIT
              }
              nameOffset = currentOffset;
              currentOffset = scanNCName(data, endOffset, currentOffset);
              if (currentOffset == nameOffset) {
                // System.out.println("abort 4b");
                return false; // REVISIT
              }
              if (currentOffset < endOffset) {
                ch = data.charAt(currentOffset);
              } else {
                ch = -1;
              }
              nameHandle = symbolTable.addSymbol(data.substring(nameOffset, currentOffset));
            }
            addToken(tokens, XPath.Tokens.EXPRTOKEN_VARIABLE_REFERENCE);
            starIsMultiplyOperator = true;
            tokens.addToken(prefixHandle);
            tokens.addToken(nameHandle);
            break;
          //
          // [37] NameTest ::= '*' | NCName ':' '*' | QName
          // [34] MultiplyOperator ::= '*'
          //
          case CHARTYPE_STAR:             // '*'
            //
            // 3.7 Lexical Structure
            //
            //  If there is a preceding token and the preceding token is not one of @, ::, (, [, , or
            //  an Operator, then a * must be recognized as a MultiplyOperator.
            //
            // Otherwise, the token must not be recognized as a MultiplyOperator.
            //
            if (starIsMultiplyOperator) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_MULT);
              starIsMultiplyOperator = false;
            } else {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_NAMETEST_ANY);
              starIsMultiplyOperator = true;
            }
            if (++currentOffset == endOffset) {
              break;
            }
            break;
          //
          // NCName, QName and non-terminals
          //
          case CHARTYPE_NONASCII: // possibly a valid non-ascii 'Letter' (BaseChar | Ideographic)
          case CHARTYPE_LETTER:
          case CHARTYPE_UNDERSCORE:
            //
            // 3.7 Lexical Structure
            //
            //  If there is a preceding token and the preceding token is not one of @, ::, (, [, , or
            //  an Operator, then an NCName must be recognized as an OperatorName.
            //
            //  If the character following an NCName (possibly after intervening ExprWhitespace) is (,
            //  then the token must be recognized as a NodeType or a FunctionName.
            //
            //  If the two characters following an NCName (possibly after intervening ExprWhitespace)
            //  are ::, then the token must be recognized as an AxisName.
            //
            //  Otherwise, the token must not be recognized as an OperatorName, a NodeType, a
            //  FunctionName, or an AxisName.
            //
            // [33] OperatorName ::= 'and' | 'or' | 'mod' | 'div'
            // [38] NodeType ::= 'comment' | 'text' | 'processing-instruction' | 'node'
            // [35] FunctionName ::= QName - NodeType
            // [6] AxisName ::= (see above)
            //
            // [37] NameTest ::= '*' | NCName ':' '*' | QName
            // [5] NCName ::= (Letter | '_') (NCNameChar)*
            // [?] NCNameChar ::= Letter | Digit | '.' | '-' | '_'  (ascii subset of 'NCNameChar')
            // [?] QName ::= (NCName ':')? NCName
            // [?] Letter ::= [A-Za-z]                              (ascii subset of 'Letter')
            // [?] Digit ::= [0-9]                                  (ascii subset of 'Digit')
            //
            nameOffset = currentOffset;
            currentOffset = scanNCName(data, endOffset, currentOffset);
            if (currentOffset == nameOffset) {
              // System.out.println("abort 4c");
              return false; // REVISIT
            }
            if (currentOffset < endOffset) {
              ch = data.charAt(currentOffset);
            } else {
              ch = -1;
            }
            nameHandle = symbolTable.addSymbol(data.substring(nameOffset, currentOffset));
            boolean isNameTestNCName = false;
            boolean isAxisName = false;
            prefixHandle = XMLSymbols.EMPTY_STRING;
            if (ch == ':') {
              if (++currentOffset == endOffset) {
                // System.out.println("abort 5");
                return false; // REVISIT
              }
              ch = data.charAt(currentOffset);
              if (ch == '*') {
                if (++currentOffset < endOffset) {
                  ch = data.charAt(currentOffset);
                }
                isNameTestNCName = true;
              } else if (ch == ':') {
                if (++currentOffset < endOffset) {
                  ch = data.charAt(currentOffset);
                }
                isAxisName = true;
              } else {
                prefixHandle = nameHandle;
                nameOffset = currentOffset;
                currentOffset = scanNCName(data, endOffset, currentOffset);
                if (currentOffset == nameOffset) {
                  // System.out.println("abort 5b");
                  return false; // REVISIT
                }
                if (currentOffset < endOffset) {
                  ch = data.charAt(currentOffset);
                } else {
                  ch = -1;
                }
                nameHandle = symbolTable.addSymbol(data.substring(nameOffset, currentOffset));
              }
            }
            //
            // [39] ExprWhitespace ::= S
            //
            while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
              if (++currentOffset == endOffset) {
                break;
              }
              ch = data.charAt(currentOffset);
            }
            //
            //  If there is a preceding token and the preceding token is not one of @, ::, (, [, , or
            //  an Operator, then an NCName must be recognized as an OperatorName.
            //
            if (starIsMultiplyOperator) {
              if (nameHandle == fAndSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_AND);
                starIsMultiplyOperator = false;
              } else if (nameHandle == fOrSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_OR);
                starIsMultiplyOperator = false;
              } else if (nameHandle == fModSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_MOD);
                starIsMultiplyOperator = false;
              } else if (nameHandle == fDivSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_OPERATOR_DIV);
                starIsMultiplyOperator = false;
              } else {
                // System.out.println("abort 6");
                return false; // REVISIT
              }
              if (isNameTestNCName) {
                // System.out.println("abort 7");
                return false; // REVISIT - NCName:* where an OperatorName is required
              } else if (isAxisName) {
                // System.out.println("abort 8");
                return false; // REVISIT - AxisName:: where an OperatorName is required
              }
              break;
            }
            //
            //  If the character following an NCName (possibly after intervening ExprWhitespace) is (,
            //  then the token must be recognized as a NodeType or a FunctionName.
            //
            if (ch == '(' && !isNameTestNCName && !isAxisName) {
              if (nameHandle == fCommentSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_NODETYPE_COMMENT);
              } else if (nameHandle == fTextSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_NODETYPE_TEXT);
              } else if (nameHandle == fPISymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_NODETYPE_PI);
              } else if (nameHandle == fNodeSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_NODETYPE_NODE);
              } else {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_FUNCTION_NAME);
                tokens.addToken(prefixHandle);
                tokens.addToken(nameHandle);
              }
              addToken(tokens, XPath.Tokens.EXPRTOKEN_OPEN_PAREN);
              starIsMultiplyOperator = false;
              if (++currentOffset == endOffset) {
                break;
              }
              break;
            }
            //
            //  If the two characters following an NCName (possibly after intervening ExprWhitespace)
            //  are ::, then the token must be recognized as an AxisName.
            //
            if (isAxisName ||
                (ch == ':' && currentOffset + 1 < endOffset &&
                    data.charAt(currentOffset + 1) == ':')) {
              if (nameHandle == fAncestorSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_ANCESTOR);
              } else if (nameHandle == fAncestorOrSelfSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_ANCESTOR_OR_SELF);
              } else if (nameHandle == fAttributeSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_ATTRIBUTE);
              } else if (nameHandle == fChildSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_CHILD);
              } else if (nameHandle == fDescendantSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_DESCENDANT);
              } else if (nameHandle == fDescendantOrSelfSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_DESCENDANT_OR_SELF);
              } else if (nameHandle == fFollowingSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_FOLLOWING);
              } else if (nameHandle == fFollowingSiblingSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_FOLLOWING_SIBLING);
              } else if (nameHandle == fNamespaceSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_NAMESPACE);
              } else if (nameHandle == fParentSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_PARENT);
              } else if (nameHandle == fPrecedingSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_PRECEDING);
              } else if (nameHandle == fPrecedingSiblingSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_PRECEDING_SIBLING);
              } else if (nameHandle == fSelfSymbol) {
                addToken(tokens, XPath.Tokens.EXPRTOKEN_AXISNAME_SELF);
              } else {
                // System.out.println("abort 9");
                return false; // REVISIT
              }
              if (isNameTestNCName) {
                // System.out.println("abort 10");
                return false; // REVISIT - "NCName:* ::" where "AxisName ::" is required
              }
              addToken(tokens, XPath.Tokens.EXPRTOKEN_DOUBLE_COLON);
              starIsMultiplyOperator = false;
              if (!isAxisName) {
                currentOffset++;
                if (++currentOffset == endOffset) {
                  break;
                }
              }
              break;
            }
            //
            //  Otherwise, the token must not be recognized as an OperatorName, a NodeType, a
            //  FunctionName, or an AxisName.
            //
            if (isNameTestNCName) {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_NAMETEST_NAMESPACE);
              starIsMultiplyOperator = true;
              tokens.addToken(nameHandle);
            } else {
              addToken(tokens, XPath.Tokens.EXPRTOKEN_NAMETEST_QNAME);
              starIsMultiplyOperator = true;
              tokens.addToken(prefixHandle);
              tokens.addToken(nameHandle);
            }
            break;
        }
      }
      if (XPath.Tokens.DUMP_TOKENS) {
        tokens.dumpTokens();
      }
      return true;
    }

    //
    // [5] NCName ::= (Letter | '_') (NCNameChar)*
    // [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
    //
    int scanNCName(String data, int endOffset, int currentOffset) {
      int ch = data.charAt(currentOffset);
      if (ch >= 0x80) {
        if (!XMLChar.isNameStart(ch))
        /*** // REVISIT: Make sure this is a negation. ***
         if ((XMLCharacterProperties.fgCharFlags[ch] &
         XMLCharacterProperties.E_InitialNameCharFlag) == 0)
         /***/ {
          return currentOffset;
        }
      } else {
        byte chartype = fASCIICharMap[ch];
        if (chartype != CHARTYPE_LETTER && chartype != CHARTYPE_UNDERSCORE) {
          return currentOffset;
        }
      }
      while (++currentOffset < endOffset) {
        ch = data.charAt(currentOffset);
        if (ch >= 0x80) {
          if (!XMLChar.isName(ch))
          /*** // REVISIT: Make sure this is a negation. ***
           if ((XMLCharacterProperties.fgCharFlags[ch] &
           XMLCharacterProperties.E_NameCharFlag) == 0)
           /***/ {
            break;
          }
        } else {
          byte chartype = fASCIICharMap[ch];
          if (chartype != CHARTYPE_LETTER && chartype != CHARTYPE_DIGIT &&
              chartype != CHARTYPE_PERIOD && chartype != CHARTYPE_MINUS &&
              chartype != CHARTYPE_UNDERSCORE) {
            break;
          }
        }
      }
      return currentOffset;
    }

    //
    // [30] Number ::= Digits ('.' Digits?)? | '.' Digits
    // [31] Digits ::= [0-9]+
    //
    private int scanNumber(XPath.Tokens tokens, String/*byte[]*/ data, int endOffset,
        int currentOffset/*, EncodingSupport encoding*/) {
      int ch = data.charAt(currentOffset);
      int whole = 0;
      int part = 0;
      while (ch >= '0' && ch <= '9') {
        whole = (whole * 10) + (ch - '0');
        if (++currentOffset == endOffset) {
          break;
        }
        ch = data.charAt(currentOffset);
      }
      if (ch == '.') {
        if (++currentOffset < endOffset) {
          /** int start = currentOffset; **/
          ch = data.charAt(currentOffset);
          while (ch >= '0' && ch <= '9') {
            part = (part * 10) + (ch - '0');
            if (++currentOffset == endOffset) {
              break;
            }
            ch = data.charAt(currentOffset);
          }
          if (part != 0) {
            /***
             part = tokens.addSymbol(data, start, currentOffset - start, encoding);
             /***/
            throw new RuntimeException("find a solution!");
            //part = fStringPool.addSymbol(data.substring(start, currentOffset));
            /***/
          }
        }
      }
      tokens.addToken(whole);
      tokens.addToken(part);
      return currentOffset;
    }

    //
    // Protected methods
    //

    /**
     * This method adds the specified token to the token list. By
     * default, this method allows all tokens. However, subclasses
     * of the XPathExprScanner can override this method in order
     * to disallow certain tokens from being used in the scanned
     * XPath expression. This is a convenient way of allowing only
     * a subset of XPath.
     */
    protected void addToken(XPath.Tokens tokens, int token)
        throws XPathException {
      tokens.addToken(token);
    } // addToken(int)

  } // class Scanner

  //
  // MAIN
  //

  /**
   * Main program entry.
   */
  public static void main(String[] argv) throws Exception {

    for (int i = 0; i < argv.length; i++) {
      final String expression = argv[i];
      System.out.println("# XPath expression: \"" + expression + '"');
      try {
        SymbolTable symbolTable = new SymbolTable();
        XPath xpath = new XPath(expression, symbolTable, null);
        System.out.println("expanded xpath: \"" + xpath.toString() + '"');
      } catch (XPathException e) {
        System.out.println("error: " + e.getMessage());
      }
    }

  } // main(String[])

} // class XPath
